<?php

namespace app\index\model;

use think\Model;

class Video extends Model
{

    public $toTypes = [
        'avi'   => [
            ['X264','libfdk_aac'] ,
            ['X264','aac'] ,
            ['X264','libmp3lame'] ,
            ['Ogg','libvorbis'] ,
            ['WebM','libvorbis'] ,
            ['WMV','wmav2'] ,
        ] ,
        'flv'   => [
            ['X264','libfdk_aac'] ,
            ['X264','aac'] ,
            ['X264','libmp3lame'] 
        ] ,
        'mp4'   => [
            ['X264','libfdk_aac'] ,
            ['X264','aac'] ,
            ['X264','libmp3lame'] ,
        ] ,
        'ogg'   => [
            ['Ogg','libvorbis'] ,
            ['WebM','libvorbis'] ,
        ] ,
        '3gp'   => [
            ['X264','libfdk_aac'] ,
            ['X264','aac'] ,
            ['WebM','libvorbis'] ,
        ] ,
        'webm'   => [
            ['WebM','libvorbis'] ,
        ] ,
        'rmvb'   => [
            ['WebM','libvorbis'] ,
        ] ,
        // 'mpeg4'   => [
        //     //['WebM','libvorbis'] , ??
        // ] ,
        // ''   => [
        //     // ['X264','libfdk_aac'] ,
        //     // ['X264','aac'] ,
        //     // ['X264','libmp3lame'] ,
        //     // ['Ogg','libvorbis'] ,
        //     // ['WebM','libvorbis'] ,
        //     // ['WMV','wmav2'] ,
        //     // ['WMV3','wmav3'] ,
        // ] ,
    ] ;

    public $rootUrl = null ;
    public $tmp     = null ;
    public $fname   = null ;
    public $tmp_img = 'img/' ;

    public $path    = [
        'ffmpeg.binaries' => '/usr/local/bin/ffmpeg',
        'ffprobe.binaries' => '/usr/local/bin/ffprobe',
    ];

    public $info    = [] ;

    # 接口接收信息
    // 队列 下载视频
    // md5视频
    // 创建该视频信息缓存
    // 处理转换信息 修改该视频信息缓存
    // 修改该视频信息缓存
    
    # 计划任务 定期清理过期文件

    # 接口
    // 文件处理进度
    // 下载

    /* 
    status 0 等待处理
    status 1 正在处理
    status 2 已经处理
     */

    // 入口
    public function process ($data=[]){
        
        $this->rootUrl = dirname( dirname( dirname( dirname(__FILE__) ) ) )."/public/" ;

        try {

            // 创建信息
            $this->info($data);

            if ( $this->info['status']==1 ) {
                exception('任务进行中');
            }
            
            // if ( $this->info['status']==2 ) {
            //     exception('任务已处理完毕');
            // }

            // 转换
            $this->run($data);

        } catch (ValidateException $e) {
            // 这是进行验证异常捕获
            $this->log([ 'err' , $e->getError() ] );
            //return json($e->getError());
            return false ;

        } catch (\Exception $e) {
            // 这是进行异常捕获
            $this->log([ 'msg' , $e->getMessage() ] );
            //return json($e->getMessage());
            return false ;
        }

        return true ;
    }

    // 处理信息
    public function info ($data=null){

        $this->R();

        if ( is_array( $data ) ) {
            asort($data) ;
            // md5视频
            $md5    = $this->remd($data) ;
        }else{
            $md5    = $data ;
        }

        // 是否存在视频记录 不存在就记录
        $status = $this->redis->HEXISTS('info',$md5);
        if ( empty( $status ) ) {
            // 任务状态
            $status = [
                'status'    => 0 ,
                'code'      => 0 ,
                'status_msg'=> '任务创建成功' ,
            ] ;
            $this->redis->HSET('status',$md5,json_encode(
                $status,JSON_UNESCAPED_UNICODE
            ));
            // 记录信息
            $data   = array_merge($data,$status) ;
            $this->redis->HSET('info',$md5,json_encode(
                $data,JSON_UNESCAPED_UNICODE
            ));

            $this->info = $data ;
        }else{
            // 查看该任务 状态
            $this->info = json_decode( $this->redis->HGET('info',$md5) , true ) ;
        }
        return $this->info ;
    }

    // 转换
    public function run ($data=[]){

        $this->get_tmp( $data['file'] );
        // 生成目录 设置存放目录
        //$this->tmp  = '' ;
        $this->mkdir($this->tmp);

        // redis
        $this->R();
        // 创建对象
        $ffmpeg = \FFMpeg\FFMpeg::create($this->path);
        $ffprobe= \FFMpeg\FFProbe::create();
        // 打开资源
        $video  = $ffmpeg->open($data['file']);
        // md5视频
        $md5    = $this->remd($data) ;
        $id     = $data['item_id'] ;

        // 循环需要转换的类型
        $this->log($id.'| --开始视频转码--');
        foreach ( $data['totype'] as $key => $type ) {
            $type   = strtolower( $type ) ;

            if ( !empty( $data['vcode'][$type] ) ) {
                // 指定 编码扩展 编码 文件
                $this->log($id.'| 指定编码扩展-'.$data['vcode']);
                list($oname,$coding)  = $data['vcode'][$type] ;
            }else

            // 是否支持该类型
            if ( !empty( $this->toTypes[$type][0] ) ) {
                list($oname,$coding)  = $this->toTypes[$type][0] ;

            }else{
                // 不支持该类型
                // 退出
                $this->log([$id.'| 记录跳过-不支持该类型 : '.$data['vname'].'.'.$type ]);
                continue;
            }

            // 提取封面
            if ( $key == 0 ) {
                $file_img       = $this->tmp_img.uniqid().'.jpg' ;
                @$video
                    ->frame( \FFMpeg\Coordinate\TimeCode::fromSeconds(1) )
                    ->save( $this->rootUrl.$file_img );
                $data['img']    = $file_img ;
            }

            // 开始转换类型
            $onames     = '\FFMpeg\Format\Video\\'.$oname ;
            if ( empty( $coding ) ) {
                $format     = new $onames() ;
            }else{
                // 选择了编码器
                $format     = new $onames( $coding ) ;
            }

            $redis  = $this->redis ;

            // 360 处理
            $video2     = $ffmpeg->open($data['file']);
            $fileUrl    = $this->tmp.$this->fname.'-360.'.$type ;
            $videos     = $ffprobe->streams( $data['file'] )->videos()->first();
            $width      = $videos->get('width');
            $height     = $videos->get('height');

            $numds      = 360 ;
            // $x          = ceil( $width * ( $numds / $height ) ) ;
            $x          = $width * ( $numds / $height ) ;
            $numdss     = $numds ;

            if ( (int) $height < (int) $numds ) {
                $numdss = $width ;
                $x      = $height ;
            }

            if ( !is_int($x) ) {
                // 求最大公约数
                $max_divisor    = $this->max_divisor($width,$height) ;
                $numdss         = $width / $max_divisor ;
                $x              = $height / $max_divisor;
            }

            // 水印
            $water      = $data['water'] ;
            $waterPath  = $water['img'] ;
            // .' && chmod -R 0777 ' . $this->rootUrl . $waterPath
            // 下载水印
            $paths      = explode('/',$waterPath) ;
            $pathss     = end( $paths ) ;
            $waterPatho = $this->rootUrl . $pathss ;
            @exec( 'cd ' . $this->rootUrl . ' && rm -rf ./' . $pathss ) ;
            @exec( 'cd ' . $this->rootUrl . ' && wget ' . $waterPath ) ;
            //$this->log( $id.'| -- : '.$waterPath );
            $waterInfo  = [
                // absolute
                'position'  => ( empty( $waterInfo['pos'] ) ? 'relative' : $waterInfo['pos'] ) ,
            ] ;
            if ( !empty( $water['top'] ) )    $waterInfo['top']     = $water['top'] ;
            if ( !empty( $water['bottom'] ) ) $waterInfo['bottom']  = $water['bottom'] ;
            if ( !empty( $water['left'] ) )   $waterInfo['left']    = $water['left'] ;
            if ( !empty( $water['right'] ) )  $waterInfo['right']   = $water['right'] ;
            $video2->filters()->watermark( $waterPatho , $waterInfo ) ;

            // 指定 分辨率
            if ( !empty( $numdss ) ) {
                $video2->filters()->resize(
                    new \FFMpeg\Coordinate\Dimension( $x , $numdss ) ,
                    \FFMpeg\Filters\Video\ResizeFilter::RESIZEMODE_SCALE_HEIGHT,
                    true
                );
            }

            // 记录进度
            $format->on('progress', function ($video2, $format, $percentage) use($redis,$md5){
                $redis->HSET('status',$md5,json_encode(
                    [
                        'status'    => 1 ,
                        'code'      => $percentage ,
                        'status_msg'=> '视频360转换中...' ,
                    ],
                    JSON_UNESCAPED_UNICODE
                ));
            });

            @$video2->save($format,$this->rootUrl.$fileUrl);
            $this->w_info($fileUrl,$md5,$data,'zh');
            $this->log($id.'| 类型 : 360-'.$oname.' OK ');

            if ( strtolower($data['vtype']) == $type ) {
                // 文件类型 和 转换到的文件类型相同 不转换
                $this->log([$id.'| 记录跳过-文件类型相同 : '.$type ]);

                // 记录文件地址
                $this->w_info( $data['file'] , $md5 , $data );

                continue;
            }

            $this->log($id.'| 格式 : '.$type.' -类型 : '.$oname.'-转换对象 : '.$onames);

            //$format->setKiloBitrate(1000)->setAudioChannels(2)->setAudioKiloBitrate(256);

            // 记录进度
            $format->on('progress', function ($video, $format, $percentage) use($redis,$md5){
                $redis->HSET('status',$md5,json_encode(
                    [
                        'status'    => 1 ,
                        'code'      => $percentage ,
                        'status_msg'=> '视频转换中...' ,
                    ],
                    JSON_UNESCAPED_UNICODE
                ));
            });

            // 保存
            $fileUrl    = $this->tmp.$this->fname.'.'.$type ;
            // $fileUrl    = $this->tmp.$data['item_id'].'-'.$coding.'-'.$oname.'.'.$type ;

            @$video->save($format,$this->rootUrl.$fileUrl);

            // 记录文件地址
            $this->w_info($fileUrl,$md5,$data);
            $this->log($id.'| 类型 : '.$oname.' OK ');

        }

        /***   预览视频水印   */
        $type_mp4           = 'mp4' ;
        list($oname,$coding)= $this->toTypes[$type_mp4][0] ;
        // 开始转换类型
        $onames             = '\FFMpeg\Format\Video\\'.$oname ;
        $format             = new $onames( $coding ) ;

        $video2     = $ffmpeg->open($data['file']);

        // 水印
        $water      = $data['water'] ;
        $waterPath  = $water['img'] ;
        // 下载水印
        $paths      = explode('/',$waterPath) ;
        $pathss     = end( $paths ) ;
        $waterPatho = $this->rootUrl . $pathss ;
        $waterInfo  = [
            'position'  => ( empty( $waterInfo['pos'] ) ? 'relative' : $waterInfo['pos'] ) ,
        ] ;
        if ( !empty( $water['top'] ) )    $waterInfo['top']     = $water['top'] ;
        if ( !empty( $water['bottom'] ) ) $waterInfo['bottom']  = $water['bottom'] ;
        if ( !empty( $water['left'] ) )   $waterInfo['left']    = $water['left'] ;
        if ( !empty( $water['right'] ) )  $waterInfo['right']   = $water['right'] ;
        $video2->filters()->watermark( $waterPatho , $waterInfo ) ;

        // 视频压缩处理
        $fileUrl    = $this->tmp.$this->fname.'-water.'.$type_mp4 ;
        $videos     = $ffprobe->streams( $data['file'] )->videos()->first();
        $width      = $videos->get('width');
        $height     = $videos->get('height');
        // 公约数
        $max_divisor= $this->max_divisor($width,$height) ;
        $numdss     = $width / $max_divisor ;
        $x          = $height / $max_divisor;
        // 指定 分辨率
        $video2->filters()->resize(
            new \FFMpeg\Coordinate\Dimension( $x , $numdss ) ,
            \FFMpeg\Filters\Video\ResizeFilter::RESIZEMODE_SCALE_HEIGHT,
            true
        );

        // 记录进度
        $format->on('progress', function ($video2, $format, $percentage) use($redis,$md5){
            $redis->HSET('status',$md5,json_encode(
                [
                    'status'    => 1 ,
                    'code'      => $percentage ,
                    'status_msg'=> '预览视频转换中...' ,
                ],
                JSON_UNESCAPED_UNICODE
            ));
        });

        // 返回
        @$video2->save($format,$this->rootUrl.$fileUrl);
        $this->w_info($fileUrl,$md5,$data,'rewater');
        $this->log($id.'| 类型 : 360-'.$oname.' OK ');
        /***   预览视频水印   */

        // 通知队列
        $info   = json_decode( $this->redis->HGET('info',$md5) , true ) ;
        \think\Queue::push('crontab/Notice@fire', $info , 'notice') ;

        $this->log($id.'| 全部转换完成');

        return true ;
    }

    //求最大公约数
    function max_divisor($a,$b) {
        $n = min($a, $b);
        for($i=$n; $i>1; $i--)
        {
            if ( is_int($a/$i) && is_int($b/$i) ) {
                return $i ;  //此处如果用echo $i;则输出结果为432；故应区分echo、return的区别
            }
        }
        return 1 ;
    }

    private $_ROOT  = null ;
    private function mkdir($savepath='') {
        if ( !$this->_ROOT ) {
            $this->_ROOT    = realpath(dirname(__file__).'/../../../public/').'/';
        }
        $dir = rtrim($this->_ROOT.$savepath,'/');
        if (is_dir($dir)) {
            return $dir ;
        }
        if (mkdir($dir, 0755, true)) {
            return $dir ;
        } else {
            $this->log("目录 {$savepath} 创建失败！");
            return false ;
        }
    }

    private function get_tmp( $img='' ){
        print_r( $img );
        $path       = explode('Uploads/',$img ) ;
        $imgAll     = end( $path ) ;

        $imgAllArr  = explode('/', $imgAll ) ;
        $imgNameExt = array_pop( $imgAllArr ) ;
        $imgPath    = implode('/', $imgAllArr );

        $path2      = explode('.', $imgNameExt ) ;
        $imgName    = reset( $path2 ) ;

        $this->tmp      = 'Uploads/'.$imgPath.'/' ;
        $this->fname    = $imgName ;
    }

    private $data   = null ;
    private function w_info($fileUrl,$md5,$data,$type='files') {

        if ( empty( $this->data ) ) {
            $this->data = $data ;
        }

        $this->R();
        // 任务进度
        $status = [
            'status'    => 2 ,
            'code'      => 100 ,
            'status_msg'=> '任务完成' ,
        ] ;
        $this->redis->HSET('status',$md5,json_encode(
            $status,JSON_UNESCAPED_UNICODE
        ));

        // 记录信息
        $files  = [] ;
        $info   = json_decode( $this->redis->HGET('info',$md5) , true ) ;
        if ( !empty( $info ) && !empty( $info[$type] ) ) {
            $files  = array_merge($files,$info[$type]);
            array_push( $files , $fileUrl );
        }else{
            $files[]        = $fileUrl ;
        }
        $this->data[$type]  = $files ;

        // 记录信息
        $this->redis->HSET('info',$md5,json_encode(
            array_merge($this->data,$status),JSON_UNESCAPED_UNICODE
        ));
    }

    public $redis   = null ;
    private function R() {
        if ( ! $this->redis) {
            $this->redis = \think\facade\Cache::store('redis')->handler();
            //\think\facade\Cache::store('default');
        }
        return $this->redis;
    }

    public function remd($data) {
        // asort($data) ;
        // $md5    = md5( json_encode($data) ) ;
        // return $md5 ;
        return $data['item_id'] ;
    }

    private function log($msg) {
        $this->R();
        if ( is_array( $msg ) ) {
            print_r($msg);
            $this->redis->LPUSH('log',json_encode($msg,JSON_UNESCAPED_UNICODE) );
        }else{
            print_r([$msg]);
            $this->redis->LPUSH('log',$msg );
        }
    }

}
